/*******************************************************************************
 * Авторское право (c) 2002, 2015 принадлежит Innoopract Informationssysteme GmbH
 * и другим компаниям.
 * Проект RAP 3.27.0. Оригинальный файл (лицензия EPL v1.0):
 * org.eclipse.rap.rwt.internal.service.ContextProvider.java
 ******************************************************************************/
package erwiki.api.rwc.context.internal.service;

import java.util.Map;
import java.util.WeakHashMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.rap.rwt.internal.application.ApplicationContextImpl;
import org.elwiki_data.WikiPage;

import erwiki.api.core.Command;
import erwiki.api.core.Engine;
import erwiki.api.core.WikiSession;
import erwiki.api.rwc.context.internal.util.ParamCheckW;

/**
 * Этот класс обеспечивает доступ всего приложения к контексту текущего обрабатываемого HTML
 * запроса.
 * <p>
 * Можно зарегистрировать контекст для потока, отличного от потока запроса (т.е. не для текущего
 * потока). Это полезно для предоставления фоновым процессам возможности получать доступ к данным,
 * хранящимся в Wiki сессии, таким же образом, как и в потоке запросов.
 * <p>
 * Примечание: В случае, если контекст уже был добавлен с помощью метода
 * <code>setContext(ServiceContext)</code>, и он попытался добавить другой контекст, используя
 * <code>setContext(ServiceContext,Thread)</code>, то исключение не будет выдано. Это связано с тем,
 * что проверка невозможна из-за особенностей реализации.</br>
 * В таком случае предпочтительным будет контекст, добавленный с помощью
 * <code>setContext(ServiceContext)</code>.
 */
public class ContextProviderW {

	// Механизм сопоставления контекста, используемый в стандартных UI запросах от клиента.
	private final static ThreadLocal<ServiceContextW> CONTEXT_HOLDER = new ThreadLocal<>();

	// Используется для сопоставления контекстов с фоновыми потоками из другого потока.
	// Примечание: эту map также можно использовать для замены CONTEXT_HOLDER, но мы предпочитаем
	// поток local для общего случая использования из-за меньшего влияния на синхронизацию.
	private final static Map<Thread, ServiceContextW> CONTEXT_HOLDER_FOR_BG_THREADS = new WeakHashMap<>();

	/**
	 * Сопоставляет заданный контекст службы с обрабатываемым в данный момент запросом (потоком).
	 * <p>
	 * Примечание: для удаления контекстов, добавленных с помощью этого метода, следует использовать
	 * {@link #disposeContext()}.
	 * 
	 * @param context
	 */
	public static void setContext(ServiceContextW context) {
		ParamCheckW.notNull(context, "context");
		if (getContextInternal() != null) {
			String msg = "Current thread has already a context instance buffered.";
			throw new IllegalStateException(msg);
		}
		CONTEXT_HOLDER.set(context);
	}

	/**
	 * Сопоставляет данный контекст службы с указанным потоком. Это используется, чтобы позволить
	 * фоновым процессам доступ к данным, хранящимся в UI сеансе.
	 * <p>
	 * Примечание: для удаления контекстов, добавленных с помощью этого метода, следует использовать
	 * {@link #disposeContext(Thread)}. Чтобы сопоставить контекст с текущим потоком, используйте
	 * <code>setContext(ServiceContext)</code>, вместо этого метода.
	 * 
	 * @param context
	 * @param thread
	 */
	public static void setContext(ServiceContextW context, Thread thread) {
		ParamCheckW.notNull(context, "context");
		ParamCheckW.notNull(thread, "thread");
		synchronized (CONTEXT_HOLDER_FOR_BG_THREADS) {
			if (CONTEXT_HOLDER_FOR_BG_THREADS.containsKey(thread)) {
				String msg = "The given thread has already a context instance mapped.";
				throw new IllegalStateException(msg);
			}
			CONTEXT_HOLDER_FOR_BG_THREADS.put(thread, context);
		}
	}

	/**
	 * Возвращает контекст службы, сопоставленный с обрабатываемым в данный момент запросом.
	 * 
	 * @return Контекст службы, сопоставленный с текущим потоком.
	 */
	public static ServiceContextW getContext() {
		ServiceContextW result = getContextInternal();
		if (result == null) {
			String msg = "No context available outside of the request processing.";
			throw new IllegalStateException(msg);
		}
		return result;
	}

	/**
	 * Возвращает статус: имеет ли текущий поток сопоставленный контекст службы.
	 * 
	 * @return Статус наличия сопоставленного контекста для текущего потока.
	 */
	public static boolean hasContext() {
		return getContextInternal() != null;
	}

	/**
	 * Освобождает текущий экземпляр контекста, находящийся в буфере. Это автоматически вызывается
	 * платформой завершить жизненный цикл контекста. Преждевременный вызов приведет к сбою
	 * обрабатываемого в данный момент жизненный цикл запроса.
	 * <p>
	 * Примечание. Только экземпляры <code>ServiceContextW</code>, сопоставленные вызовом
	 * <code>setContext()</code> из работающего потока, можно удалить с помощью этого метода. Контексты
	 * которые были зарегистрированы <code>setContext(Thread,ServiceContext)</code>, должны быть удалены
	 * используя <code>disposeContext(Thread)</code>.
	 */
	public static void disposeContext() {
		ServiceContextW context = CONTEXT_HOLDER.get();
		if (context != null) {
			if (!context.isDisposed()) {
				context.dispose();
			}
		}
		// НЕ ПЕРЕМЕЩАТЬ ЭТУ СТРОКУ В БЛОК IF
		// Это может привести к утечке памяти, поскольку disposeContext() используется для удаления
		// контекста *и* отсоединения контекста от потока.
		releaseContextHolder();
	}

	public static boolean releaseContextHolder() {
		if (CONTEXT_HOLDER.get() != null) {
			CONTEXT_HOLDER.set(null);
			return false;
		}
		synchronized (CONTEXT_HOLDER_FOR_BG_THREADS) {
			CONTEXT_HOLDER_FOR_BG_THREADS.remove(Thread.currentThread());
		}
		return true;
	}

	private static ServiceContextW getContextInternal() {
		ServiceContextW result = CONTEXT_HOLDER.get();
		if (result == null) {
			synchronized (CONTEXT_HOLDER_FOR_BG_THREADS) {
				result = CONTEXT_HOLDER_FOR_BG_THREADS.get(Thread.currentThread());
			}
		}
		return result;
	}

	/**
	 * Возвращает экземпляр <code>Engine</code>.
	 */
	public static Engine getEngine() {
		return getContext().getEngine();
	}

	/**
	 * Возвращает WikiSession, связанную с обрабатываемым в данный момент запросом.
	 */
	public static WikiSession getWikiSession() {
		return getContext().getWikiSession();
	}

	/**
	 * Возвращает <code>HttpServletRequest</code>, который обрабатывается в данный момент.</br>
	 * Это вспомогательный метод.
	 */
	public static HttpServletRequest getRequest() {
		return getContext().getRequest();
	}

	/**
	 * Возвращает <code>HttpServletResponse</code>, сопоставленный с обрабатываемым в данный момент
	 * запросом.</br>
	 * Это вспомогательный метод.
	 */
	public static HttpServletResponse getResponse() {
		return getContext().getResponse();
	}

	public static WikiPage getPage() {
		return getContext().getPage();
	}

	public static void setPage(WikiPage page) {
		getContext().setPage(page);
	}

	public static int getPageVersion() {
		return getContext().getPageVersion();
	}
	
	public static void setPageVersion(int pageVersion) {
		getContext().setPageVersion(pageVersion);
	}
	
	public static Command getCommand() {
		return getContext().getCommand();
	}

	public static void setCommand(Command command) {
		getContext().setCommand(command);
	}

	public static void pushCommand() {
		getContext().pushCommand();
	}

	public static void popCommand() {
		getContext().popCommand();
	}

}
